const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const { VARIABLE_KIND, VARIABLE_SCOPE_KIND } = require('smart-rocket-unit');
const DefaultReport = require('../base-report');
const ProgressIndicator = require('../../../progress-indicator');
const utility = require('../../../utility');
const testModels = require('../../../tests');
const functionModels = require('../../../functions');
const classCreator = require('./../base-report-test-case-transformer');
const logger = require('../../../logger');
const { versionType } = require('../../../project-versions');

const testPassStatusKind = {
  pass: 1, // 通过
  nPass: 2, // 不通过
};
class Report134 extends DefaultReport {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format = null, complete = false) {
    super(versionId, locale, version, project, creator, outputDir, null, format);
    this.baseDir = __dirname;
    this.testInstructions = {};
    this.testRecords = {};
    this.complete = complete;
    this.ReportTestCaseTransformer = classCreator('unitTest');
    this.fileMap = new Map();
    // 报告无法识别的异常字符
    // eslint-disable-next-line no-control-regex
    this.unusualCharReg = /[\x00-\x08\x0B\x0C\x0E-\x1F]/g;
  }

  /**
   * 报告生成
   */
  async generate(progressIndicator = new ProgressIndicator()) {
    const [progress1, progress2] = progressIndicator.split([0.5, 0.5]);
    const result = {
      cachePath: this.outputDir,
    };
    this.overridingGetAreas();
    await this.spliteByFile(null, progress1);
    await this.generateTestInstructions();
    await this.generateTestRecords();
    progress2.advance(0.5);
    progress2.finish();
    const fileName = `${this.project.projectName}-${this.version.versionName}.${this.getReportName()}.${this.format}.zip`;
    result.reportPath = path.join(this.outputDir, fileName);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async handleFile(file, versionDir) {
    this.fileMap.set(file._id.toString(), file.fileName);
    const result = await super.handleFile(file, versionDir);
    this.collectTestInstructions(result);
    this.collectTestRecords(result);
  }

  // 测试说明报告
  async generateTestInstructions() {
    const template = 'test_instructions';
    const output = path.join(
      this.outputDir,
      this.project.projectName,
      this.version.versionName,
      `${this.project.projectName}软件单元测试说明`
    );
    this.testInstructions.percentByType = {};
    const all = this.testInstructions.countByType.ALL || 0;
    this.testInstructions.percentByType.ALL = all ? '100%' : '-';
    [
      'GN', // 功能
      'JK', // 借口
      'BJ', // 边界
      'LJ', // 逻辑
      'XN', // 性能
    ].forEach((key) => {
      if (all === 0) {
        this.testInstructions.percentByType[key] = '-';
        return;
      }
      const cnt = this.testInstructions.countByType[key] || 0;
      this.testInstructions.percentByType[key] = `${Math.round((cnt / all) * 100, 10)}%`;
    });
    this.testInstructions.testItems = this.testInstructions.testItems.map((item, idx) => ({
      index: idx + 1,
      ...item,
    }));
    return this.generateReport(template, this.testInstructions, output);
  }

  /**
   * 变量展开平铺
   * @param {Object} variable 变量数据
   * @param {Array[String]} resVariables 平铺好的变量
   * @param {Number} scope 输入输出域
   * @param {Number} resultKind 测试用例执行状态
   */
  variableRecursion(variable, resVariables = [], scope) {
    if (variable.children) {
      variable.children.forEach((childVariable) => {
        if (variable.baseVariableName) {
          childVariable.baseVariableName = variable.baseVariableName;
        }
        this.variableRecursion(childVariable, resVariables, scope);
      });
    } else {
      let newVariableName = variable.fieldPath.replace(/output\.|variables\.|params\.|malloc\.|global\.|statics\.|\.@value|\.@init|\.@ctor/g, '');
      // 局部静态变量需要特殊处理
      if (variable.kind === VARIABLE_KIND.statics) {
        newVariableName = newVariableName.replace(/^[\d]+|^\d+$/, `${variable.baseVariableName}`);
      }
      // 处理数组元素
      const arryIndexReg = /\.[\d]+/g;
      if (newVariableName.match(arryIndexReg)) {
        const arrayIndexs = newVariableName.match(arryIndexReg);
        arrayIndexs.forEach((arryIndexStr) => {
          newVariableName = newVariableName.replace(arryIndexStr, `[${arryIndexStr.split('.')[1]}]`);
        });
      }
      // 处理简单返回值
      if (newVariableName === '%') {
        newVariableName = 'return';
      }
      // 处理复杂结构返回值
      if (newVariableName.match(/%\./)) {
        newVariableName = newVariableName.replace(/%\./, 'return ');
      }
      newVariableName = newVariableName.replace(this.unusualCharReg, '');
      let value = variable.data && variable.data['@value'] ? variable.data['@value'] : '';
      const result = variable.data && variable.data['@result'] ? variable.data['@result'] : '';
      // 报告不显示没有填值的变量
      if (!this.complete && !value) {
        return;
      }
      if (scope === VARIABLE_SCOPE_KIND.output && !value && result) {
        value = result;
      }
      if (value || result) {
        resVariables.push({
          variableName: newVariableName,
          typeName: variable.typeName,
          value,
          result,
        });
      }
    }
  }

  // 重写对象的getAreas方法，设置要组装的报告数据
  overridingGetAreas() {
    this.ReportTestCaseTransformer.prototype.getAreas = function getAreas() {
      if (this.stubsData && this.stubsData.length > 0) {
        this.stubsData.forEach((stub) => {
          if (stub.name) {
            stub.name += (testModels.stubKind.code === stub.currentStubKind ? '被代码打桩' : '被打桩');
          }
        });
      }
      return [this.stubsData];
    };
  }

  async collectTestRecords(fileTestData) {
    const { file, functions, typeSystem } = fileTestData;
    if (!this.testRecords.files) {
      this.testRecords.files = [];
    }
    const collectFile = {
      fileName: file.fileName,
      functions: [],
    };
    if (!functions || functions.length === 0) {
      return;
    }
    const newFuncs = functions.sort((a, b) => (a.index > b.index ? 1 : -1));
    await utility.arrayChunkOperation(newFuncs, 20, async (func) => {
      const collectFunc = {
        functionName: func.functionName,
        tests: [],
      };
      if (!func.tests || func.tests.length === 0) {
        return;
      }
      const { branches, decisions, nodes } = await functionModels.getFunctionCFG(func._id);
      const functionVariables = functionModels.getFunctionVariableJson(func.variables);
      let testIndex = 1;
      func.tests.forEach((test) => {
        if (test.resultKind === testModels.resultKind.unknown) {
          return;
        }
        const newTestcase = testModels.getJson(test, this.i18n);
        const transformer = new this.ReportTestCaseTransformer(
          newTestcase,
          functionVariables,
          typeSystem,
          this.i18n
        );
        try {
          transformer.transform(this.complete);
        } catch (error) {
          logger.error(`testCase transform error: fileName: ${this.fileMap.get(func.fileId.toString())} function name: ${func.functionName} test case number: ${test.number}:`, error);
          return;
        }
        const testcaseId = test._id.toString();
        const branchExprs = [];
        if (branches
          && Object.keys(branches).length > 0
          && nodes
          && Object.keys(nodes).length > 0
          && decisions
          && Object.keys(decisions).length > 0) {
          Object.keys(branches).forEach((branchKey) => {
            const branch = branches[branchKey];
            if (branch.decisionId === undefined || !decisions[branch.decisionId]) {
              return;
            }
            const { expr } = decisions[branch.decisionId];
            Object.keys(branch.branches).forEach((nodeId) => {
              const branchNode = branch.branches[nodeId];
              if (branchNode
                && branchNode.kind === 'BOOLEAN'
                && branchNode.testIds
                && branchNode.testIds[testcaseId]
                && branchNode.testIds[testcaseId] > 0
                && nodes[nodeId]
                && nodes[nodeId].begin) {
                branchExprs.push({
                  expr,
                  value: branchNode.value === '1',
                });
              }
            });
          });
        }
        const newInputDetails = {
          variables: [],
          stubs: [],
        };
        // output顺序 函数返回值，全局变量，局部静态变量，指针目标
        const newOutputDetails = [];
        // 处理桩函数打桩情况
        transformer.input.forEach((input) => {
          // 处理异常格式的字符，防止导入报告失败
          input.variable = input.variable.replace(this.unusualCharReg, '');
          // 处理代码打桩
          if (input.values) {
            input.value = '';
          }
          // 处理变量值显示格式
          if (input.value) {
            input.value = ` = ${input.value}`;
          }
          if (input.category && input.variable) {
            newInputDetails.stubs.push({
              category: `${input.category}:`,
              value: '',
              variable: '',
            });
            newInputDetails.stubs.push({
              category: '',
              value: input.value,
              variable: input.variable,
              values: input.values,
            });
          } else {
            newInputDetails.stubs.push(input);
          }
        });
        // 处理输入
        [
          ...transformer.paramVariablesData,
          ...transformer.globalVariablesData,
          ...transformer.staticsData,
        ].forEach((variable) => {
          // 局部静态变量需要特殊处理
          if (variable.kind === VARIABLE_KIND.statics) {
            variable.baseVariableName = variable.name;
          }
          this.variableRecursion(variable, newInputDetails.variables, VARIABLE_SCOPE_KIND.input);
        });
        // 处理输出
        [
          ...transformer.returnValueData,
          ...transformer.globalOutputData,
          ...transformer.staticsOutputData,
          ...transformer.mallocOutputData].forEach((variable) => {
          // 局部静态变量需要特殊处理
          if (variable.kind === VARIABLE_KIND.statics) {
            variable.baseVariableName = variable.name;
          }
          this.variableRecursion(variable, newOutputDetails, VARIABLE_SCOPE_KIND.output);
        });
        const resultKind = [
          testModels.resultKind.succeed,
          testModels.resultKind.noResult,
          testModels.resultKind.nonStrictSucceed,
        ].includes(test.resultKind) ? testPassStatusKind.pass : testPassStatusKind.nPass;
        collectFunc.tests.push({
          testIndex,
          typeName: newTestcase.typeName === 'BJ' ? 1 : 0,
          description: newTestcase.description || '',
          prelude: transformer.prelude || [],
          resultKind,
          testBranches: branchExprs,
          input: newInputDetails,
          output: newOutputDetails,
        });
        testIndex += 1;
      });
      if (collectFunc.tests.length > 0) {
        collectFile.functions.push(collectFunc);
      }
    });
    if (collectFile.functions.length > 0) {
      this.testRecords.files.push(collectFile);
    }
  }

  collectTestInstructions(fileResult) {
    const { functions } = fileResult;
    // 测试项列表
    const testItems = functions.reduce((arr, fun) => {
      arr.push(...fun.tests.map((t, idx) => {
        this.testInstructions.countByType = this.testInstructions.countByType || {};
        this.testInstructions.countByType.ALL = this.testInstructions.countByType.ALL || 0;
        this.testInstructions.countByType.ALL += 1;
        const typeName = t.typeName || 'GN';
        this.testInstructions.countByType[typeName] = this.testInstructions.countByType[typeName] || 0;
        this.testInstructions.countByType[typeName] += 1;
        return {
          functionDesc: fun.description,
          functionIdentifier: `${fun.functionName}_LLR`,
          functionName: fun.functionName,
          testDesc: t.description,
          testIdentifier: `${fun.functionName}_UTR_${idx + 1}${t.typeName === 'BJ' ? '（FN/LG/IF/BD）' : '（FN/LG/IF）'}`,
        };
      }));
      return arr;
    }, []);
    this.testInstructions.testItems = this.testInstructions.testItems || [];
    this.testInstructions.testItems.push(...testItems);
  }

  // 测试记录集报告
  async generateTestRecords() {
    const output = path.join(this.outputDir, this.project.projectName, this.version.versionName);
    this.testRecords.exportTime = moment().format('YYYYMMDD');
    this.testRecords.userName = this.creator.username;
    const date = moment.unix(this.version._id.generationTime);
    this.testRecords.createVersionTime = date.format('YYYYMMDD');
    if (this.testRecords.files) {
      let nPassIndex = 1;
      let index = 1;
      const len1 = this.testRecords.files.length;
      for (let i = 0; i < len1; i += 1) {
        if (this.testRecords.files[i].functions) {
          const len2 = this.testRecords.files[i].functions.length;
          for (let j = 0; j < len2; j += 1) {
            if (this.testRecords.files[i].functions[j].tests) {
              const len3 = this.testRecords.files[i].functions[j].tests.length;
              for (let k = 0; k < len3; k += 1) {
                if (this.testRecords.files[i].functions[j].tests[k].resultKind === testPassStatusKind.nPass) {
                  this.testRecords.files[i].functions[j].tests[k].nPassIndex = nPassIndex;
                  nPassIndex += 1;
                }
                this.testRecords.files[i].functions[j].tests[k].index = index;
                index += 1;
              }
            }
          }
        }
      }
    }
    await this.generateReport('test_records', this.testRecords, path.join(output, `${this.project.projectName}软件单元测试记录集X.YZ`));
    await this.generateReport('test_records_testcases', this.testRecords, path.join(output, `${this.project.projectName}软件单元测试用例集X.YZ`));
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest;
  }
}

module.exports = Report134;
